/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.codeexecutor;

import com.googlecode.datawander.i18n.I18n;
import com.googlecode.datawander.shemareader.MetaClass;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;


/**
 * Code loader use to load compiled code in relational object mapping process.
 * @author Marcin Stachniuk
 */
public class MyGeneratedCodeLoader extends ClassLoader {

    private static final Logger logger = Logger.getLogger(MyGeneratedCodeLoader.class);

    private String classDirectory;
    private Map<String, Class> classMap = new HashMap<String, Class>();
    private String db4oFilename;

    public MyGeneratedCodeLoader(String classDirectory) {
        this.classDirectory = classDirectory;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        logger.trace("findClass("+name+")");

        try {
            name = name.replace('.', File.separatorChar);
            String file = (classDirectory.endsWith("" + File.separatorChar) ? classDirectory : classDirectory + File.separatorChar)
                    + name + ".class";

            String message = I18n.getString("fileclasstoload");
            message = String.format(message, file);
            logger.debug(message);

            int len = (int)(new File(file).length());

            FileInputStream in = new FileInputStream(file);

            byte classBytes[] = new byte[len];
            in.read(classBytes);
            in.close();

            logger.debug(I18n.getString("loadwholefile"));

            //Class cl = defineClass(name, classBytes, 0, classBytes.length);
            Class cl = defineClass(name.replace(File.separator, "."), classBytes, 0, classBytes.length);
	    if (cl == null) {
                logger.error(I18n.getString("createdclassisnull"));
                throw new ClassNotFoundException(name);
            }
	    return cl;
        } catch (FileNotFoundException ex) {
            String message = I18n.getString("cantfindfileclassindirectory");
            message = String.format(message, name, classDirectory);
            logger.error(message, ex);
            throw new ClassNotFoundException(message);
        } catch (IOException ex) {
            String message = I18n.getString("filereaderror");
            message = String.format(message, name, classDirectory);
            logger.error(message, ex);
            throw new ClassNotFoundException(message);
        }
    }

    /**
     * Try load all clases from tabOfAllClassNames.
     * @throws ClassNotFoundException If is impossible to load class.
     */
    public void loadAllCompiledClasses() throws ClassNotFoundException {
        logger.trace("loadAllClass(...)");

        String [] tabGeneratedClesses = generateNamesOfClassesFromDirectory(classDirectory);

//        classMap.put("OracleConnector", loadClass("OracleConnector"));
//        logger.debug("Zaladowano klase: OracleConnector");
//
//        classMap.put("OracleConnector", loadClass("OracleConnector"));
//        logger.debug("Zaladowano klase: OracleConnector");
        //Db4oUtil

        for(String name : tabGeneratedClesses) {
            classMap.put(name, loadClass(name));
            String message = I18n.getString("loadedname");
            message = String.format(message, name);
            logger.debug(message);
        }

        logger.info(I18n.getString("loadAllClassesSuccess"));
    }

    /**
     * Invoke method to get all records from database of className class.
     * @param className Name of class
     * @throws CodeRunException esli wystapil blad wywolania metody.
     * @return geted records.
     */
    public Object invokeLoadAllRecords(String className) throws CodeRunException {
        logger.trace("invokeLoadAllRecords("+className+");");
        
        Class cl = classMap.get(className);

        try {
            Method methodLoadAllRecords = cl.getDeclaredMethod("loadAllRecords");

            Object obj = methodLoadAllRecords.invoke(null);

            String message = I18n.getString("loadallrecordsbyclass");
            message = String.format(message, className);
            logger.info(message);
            return obj;
        } catch(NoSuchMethodException e) {
            throw new CodeRunException(e.getMessage());
        } catch(IllegalAccessException e) {
            throw new CodeRunException(e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new CodeRunException(e.getMessage());
        } catch (InvocationTargetException e) {
            logger.error(I18n.getString("invokedmethodthrowexception"), e);
            throw new CodeRunException(e.getMessage());
        }

    }

    /**
     * Invoke methods to set prametrs to connet do relational database
     * @param url ULR adres to connect to relational database.
     * @param username Database username.
     * @param password Password.
     * @throws CodeRunException When is error in operation
     */
    public void setRealtionalDatabaseParametrs(String url, String username, String password) throws CodeRunException {
        logger.trace("setRealtionalDatabaseParametrs("+url+", "+username+", "+password+");");

        Class cl = classMap.get("OracleConnector");
        if(cl == null) {
            String message = I18n.getString("unabletoloadoracleconnectiorclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        try {
            Method methodSetUrl = cl.getMethod("setUrl", String.class);
            methodSetUrl.invoke(null, url);

            Method methodSetUsername = cl.getMethod("setUsername", String.class);
            methodSetUsername.invoke(null, username);

            Method methodSetPassword = cl.getMethod("setPassword", String.class);
            methodSetPassword.invoke(null, password);

            logger.info(I18n.getString("setConectionParametrsWithDatabaseSuccess"));
        } catch(NoSuchMethodException e) {
            throw new CodeRunException(e.getMessage());
        } catch(IllegalAccessException e) {
            throw new CodeRunException(e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new CodeRunException(e.getMessage());
        } catch (InvocationTargetException e) {
            logger.error(I18n.getString("invokedmethodthrowexception"), e);
            throw new CodeRunException(e.getMessage());
        }
    }

    /**
     * Set object database filename
     * @param filename Path to file where is object database
     * @throws CodeRunException When is error in operation
     */
    public void setDb4oFilename(String filename) throws CodeRunException {
        logger.trace("setDb4oFilename("+filename+");");

        db4oFilename = filename;

        Class cl = classMap.get("Db4oUtil");
        if(cl == null) {
            String message = I18n.getString("unabletoloaddb4outilclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        try {
            Method methodSetUrl = cl.getMethod("setDatabaseFilename", String.class);
            methodSetUrl.invoke(null, filename);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninsetdb4ofilename");
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

    /**
     * Ivoke method to save all object in object database
     * @param className name of class
     * @param objTab tab with objects to save in objectdatabase
     * @throws CodeRunException When is error in operation
     */
    public void saveObjectsInDb4o(String className, Object[] objTab) throws CodeRunException {
        logger.trace("saveObjectsInDb4o("+className+", objTab"+");");

        Class cl = classMap.get(className);
        if(cl == null) {
            String message = I18n.getString("cannotloadclass");
            message = String.format(message, className);
            logger.error(message);
            throw new CodeRunException(message);
        }

        try {
            Object tab = Array.newInstance(cl, objTab.length);
            Method methodSetUrl = cl.getMethod("saveObjectsInDb4o", tab.getClass());

            if(tab instanceof Object[]) {
                logger.trace(I18n.getString("castobjecttotabofobjects"));

                Object [] tabOfObjects = (Object[]) tab;
                String message = I18n.getString("sizeofobjecttab");
                message = String.format(message, objTab.length);
                logger.debug(message);

                message = I18n.getString("prescribe");
                for(int i=0; i < objTab.length; i++) {
                    tabOfObjects[i] = objTab[i];
                    logger.debug(message+objTab[i].toString());
                }
                //throw new ClassCastException("aaa");
                methodSetUrl.invoke(null, tab);
                
                message = I18n.getString("saveallrecordsinobjectdatabase");
                message = String.format(message, className);
                logger.info(message);
            }

        } catch(java.lang.ClassCastException e) {
            String message = I18n.getString("classcastexceptioninsaveindb4o");
            message = String.format(message, db4oFilename);
            logger.error(message, e);
            throw new CodeRunException(message);
        } catch(RuntimeException e) {
            String message = I18n.getString("classcastexceptioninsaveindb4o");
            message = String.format(message, db4oFilename);
            logger.error(message, e);
            throw new CodeRunException(message);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninmethod");
            message = String.format(message, "saveObjectsInDb4o");
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

    /**
     * Generate tab of Strings with class names on the basis of folders and
     * classess files.
     * @param classDirectory root folder for classess
     * @return Tab of Strings.
     */
    private String[] generateNamesOfClassesFromDirectory(String classDirectory) {
        logger.trace("generateNamesOfClassesFromDirectory("+classDirectory+")");
        File file = new File(classDirectory);

        List<String> list = new ArrayList<String>();

        File [] files = file.listFiles();
        for(File f : files) {
            generateNamesOfClassFromDir(classDirectory, f, list);
        }

        return list.toArray(new String[0]);
    }

    /**
     * Generate name of class on the basis of folder and class file and add
     * that name to list
     * @param classDirectory root folder for classess
     * @param f filename
     * @param list results list
     */
    private void generateNamesOfClassFromDir(String classDirectory, File f, List<String> list) {
        logger.trace("generateNamesOfClassFromDir("+classDirectory+", "+f.getPath()+", list);");
        if(f.isDirectory()) {
            for(File file : f.listFiles()) {
                generateNamesOfClassFromDir(classDirectory, file, list);
            }
        } else {
            if(f.isFile() && f.getName().endsWith(".class")) {
                String className = f.getName().substring(0, f.getName().length() - 6);
                String packageName = f.getPath().substring(0, (f.getPath().length() - f.getName().length()));    // filename
                packageName = packageName.substring(classDirectory.length());           // cut first part of name
                packageName.replace(File.pathSeparatorChar, '.');
                list.add(packageName+className);
            }
        }
    }

    public void closeDb4o() throws CodeRunException {
        logger.trace("clodeDb4o();");

        Class cl = classMap.get("Db4oUtil");
        if(cl == null) {
            String message = I18n.getString("unabletoloaddb4outilclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        String methodName = "shutdown";
        try {
            Method methodSetUrl = cl.getMethod(methodName);
            methodSetUrl.invoke(null);

            methodName = "closeObjectContainer";
            methodSetUrl = cl.getMethod(methodName);
            methodSetUrl.invoke(null);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninmethod");
            message = String.format(message, methodName);
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

    public void updateManyToManyObjects(MetaClass mc) throws CodeRunException {
        logger.trace("updateManyToManyObjects("+mc.getClassName()+");");

        Class cl = classMap.get(mc.getClassName());
        if(cl == null) {
            String message = I18n.getString("cannotloadclass");
            message = String.format(message, mc.getClassName());
            logger.error(message);
            throw new CodeRunException(message);
        }

        String methodName = "updateManyToManyObjects";
        try {
            Method methodSetUrl = cl.getMethod(methodName);
            methodSetUrl.invoke(null);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninmethod");
            message = String.format(message, methodName);
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

    public void defragObjectDatabase() throws CodeRunException {
        logger.trace("defragObjectDatabase();");

        Class cl = classMap.get("Db4oUtil");
        if(cl == null) {
            String message = I18n.getString("unabletoloaddb4outilclass");
            logger.error(message);
            throw new CodeRunException(message);
        }

        String methodName = "defragDatabase";
        try {
            Method methodSetUrl = cl.getMethod(methodName);
            methodSetUrl.invoke(null);
        } catch (Exception e) {
            String message = I18n.getString("exceptioninmethod");
            message = String.format(message, methodName);
            logger.error(message, e);
            throw new CodeRunException(message);
        }
    }

}
